package com.luo.sc.oidc.authserver.config;

import com.luo.sc.oidc.authserver.constant.Oauth2Constants.PROVIDER_SETTINGS;
import com.luo.sc.oidc.authserver.controller.consent.AuthorizationConsentController;
import com.luo.sc.oidc.authserver.enums.PasswordEncoderEnum;
import com.luo.sc.oidc.authserver.handler.login.UniLoginUserDetailsService;
import com.luo.sc.oidc.authserver.handler.oidc.DefaultOidcTokenCustomer;
import com.luo.sc.oidc.authserver.handler.oidc.DefaultOidcUserInfoMapper;
import com.luo.sc.oidc.authserver.handler.oidc.OidcCustomProviderConfigurationEndpointFilter;
import com.luo.sc.oidc.authserver.handler.token.OAuth2TokenEndpointAuthenticationResponseHandler;
import com.luo.sc.oidc.authserver.service.JdbcOidcAuthorizationService;
import com.luo.sc.oidc.authserver.service.OidcAuthorizationService;
import com.luo.sc.oidc.authserver.utils.Jwks;
import com.luo.sc.oidc.authserver.utils.ObjectPostProcessorUtils;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.server.authorization.*;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2CustomAuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2CustomClientAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2CustomPassowordAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.DefaultOAuth2TokenEndpointAuthenticationResponseHandlerImpl;
import org.springframework.security.oauth2.server.authorization.web.authentication.*;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;

/**
 * OAuth2 AuthServer配置
 *
 * @author luo
 * @date 2022-02-17
 */
@Slf4j
@EnableConfigurationProperties(Oauth2ServerProps.class)
public class AuthorizationServerConfig {

    private Oauth2ServerProps oauth2ServerProps;

    private DefaultOidcUserInfoMapper.OidcUserInfoMapperExtend oidcUserInfoMapperExtend;
    private DefaultOidcTokenCustomer.AbstractOidcTokenCustomerExtend oidcTokenCustomerExtend;

    public AuthorizationServerConfig(Oauth2ServerProps oauth2ServerProps,
                                     @Nullable DefaultOidcTokenCustomer.AbstractOidcTokenCustomerExtend oidcTokenCustomerExtend,
                                     @Nullable DefaultOidcUserInfoMapper.OidcUserInfoMapperExtend oidcUserInfoMapperExtend) {
        this.oauth2ServerProps = oauth2ServerProps;
        this.oidcTokenCustomerExtend = oidcTokenCustomerExtend;
        this.oidcUserInfoMapperExtend = oidcUserInfoMapperExtend;
    }

    /**
     * 以最高优先级配置Authorization Server
     *
     * @param http                                             Security构建器
     * @param jwtEncoder                                       JWT编码器
     * @param registeredClientRepository                       客户端注册管理器
     * @param authorizationService                             认证信息管理服务
     * @param uniLoginUserDetailsService                       通用登录服务
     * @param oAuth2TokenEndpointAuthenticationResponseHandler OAuth2 token端点结果处理器
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
                                                                      JwtEncoder jwtEncoder,
                                                                      RegisteredClientRepository registeredClientRepository,
                                                                      OAuth2AuthorizationService authorizationService,
                                                                      UniLoginUserDetailsService uniLoginUserDetailsService,
                                                                      OAuth2TokenEndpointAuthenticationResponseHandler oAuth2TokenEndpointAuthenticationResponseHandler) throws Exception {
        OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer<>();
        RequestMatcher endpointsMatcher = authorizationServerConfigurer
                .getEndpointsMatcher();

        http
                //仅拦截OAuth2 Authorization Server的相关endpoint
                .requestMatcher(endpointsMatcher)
                //开启请求认证
                .authorizeHttpRequests(authorizeRequests ->
                        authorizeRequests
                                .anyRequest().authenticated()
                )
                //支持跨域(放过OPTIONS请求，集成端需自定义CorsFilter Bean设置CORS相关配置)
                .cors().and()
                //禁用OAuth2 Server相关endpoint的CSRF防御
                .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
                //需开启OAuth2 Resource Server支持OIDC /userinfo 的Bearer accessToken鉴权（否则401）
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
                //OAuth2相关设置
                .apply(authorizationServerConfigurer)
                //扩展OAuth2 Authorization认证入口
                .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
                        //设置确认界面uri
                        .consentPage(this.oauth2ServerProps.getConsentPageUrl())
                )
                //扩展OAuth2 Client认证（/oauth2/token|introspect|revoke）
                .clientAuthentication(clientAuthentication -> clientAuthentication
                        //扩展Oauth2 client认证 参数转换器 - 支持RefreshToken无需client_secret认证
                        .authenticationConverter(new DelegatingAuthenticationConverter(
                                Arrays.asList(
                                        new JwtClientAssertionAuthenticationConverter(),
                                        new ClientSecretBasicAuthenticationConverter(),
                                        new ClientSecretPostAuthenticationConverter(),
                                        /** 添加自定义RefreshToken请求解析器（支持不提供client_secret刷新token） */
                                        new PublicClientRefreshTokenAuthenticationConverter(),
                                        new PublicClientAuthenticationConverter())))
                        //扩展OAuth2 Token端点 客户端认证逻辑 - 支持refresh_token不提供client_secret认证（支持PKCE code模式）
                        .authenticationProvider(new OAuth2CustomClientAuthenticationProvider(registeredClientRepository, authorizationService))
                )
                //扩展 OAuth Token端点 - 支持grant_type=password及对应的认证参数authParams提取
                .tokenEndpoint(tokenEndpoint -> tokenEndpoint
                        //支持grant_type=password及对应的认证参数authParams提取
                        .accessTokenRequestConverter(new DelegatingAuthenticationConverter(
                                Arrays.asList(
                                        new OAuth2AuthorizationCodeAuthenticationConverter(),
                                        new OAuth2RefreshTokenAuthenticationConverter(),
                                        /** 添加自定义password授权模式请求解析器（支持grant_type=password） */
                                        new OAuth2PasswordAuthenticationConverter(),
                                        new OAuth2ClientCredentialsAuthenticationConverter())))
                        //设置自定义认证失败处理器，通过响应Json返回结果（兼容grant_type=password模式）
                        .errorResponseHandler(oAuth2TokenEndpointAuthenticationResponseHandler)
                        //设置自定义认证成功处理器（默认兼容原逻辑）
                        .accessTokenResponseHandler(oAuth2TokenEndpointAuthenticationResponseHandler)
                )
                //OIDC相关设置
                .oidc(oidc -> oidc
                        //UserInfo endpoint相关设置
                        .userInfoEndpoint(userInfo -> userInfo
                                //设置默认用户信息转换器
                                .userInfoMapper(new DefaultOidcUserInfoMapper(this.oidcUserInfoMapperExtend)))
                )
                //替换OidcProviderConfigurationEndpointFilter默认实现为OidcOpConfigurationEndpointFilter
                .withObjectPostProcessor(ObjectPostProcessorUtils.objectPostReturnNewObj(
                        OncePerRequestFilter.class,
                        OidcProviderConfigurationEndpointFilter.class,
                        new OidcCustomProviderConfigurationEndpointFilter(this.providerSettings())))
                //扩展OAuth2 Token端点 - 支持在PKCE模式下也生成refresh_token
                //使用替换模式，未在tokenEndpoint.authenticationProvider处设置，
                //原因：存在多个AuthProvider（OAuth2AuthorizationCodeAuthenticationProvider, OAuth2RefreshTokenAuthenticationProvider, OAuth2ClientCredentialsAuthenticationProvider），避免覆盖
                .withObjectPostProcessor(ObjectPostProcessorUtils.objectPostConvertObj(
                        AuthenticationProvider.class,
                        OAuth2AuthorizationCodeAuthenticationProvider.class,
                        oAuth2AuthorizationCodeAuthenticationProvider -> new OAuth2CustomAuthorizationCodeAuthenticationProvider(authorizationService, http.getSharedObject(JwtEncoder.class))));

        //添加自定义Token Endpoint grant_type=password认证器
        http.authenticationProvider(new OAuth2CustomPassowordAuthenticationProvider(authorizationService, uniLoginUserDetailsService, jwtEncoder));

        //return http.formLogin(Customizer.withDefaults()).build();
        return http.formLogin(form -> form
                //替换默认配置（兼容自定义loginPageUrl时的oauth2登录跳转）
                .loginPage(this.oauth2ServerProps.getLoginPageUrl())
        ).build();
    }


    /**
     * Consent Page相关配置
     */
    @ConditionalOnProperty(name = "spring.security.oauth2.authserver.auto-config-consent-page", matchIfMissing = true)
    @ComponentScan(basePackageClasses = AuthorizationConsentController.class)
    @Configuration
    public static class ConsentPageConfig {

    }

    /**
     * 自定义OAuth2 Token端点响应结果处理器
     */
    @Bean
    @ConditionalOnMissingBean
    public OAuth2TokenEndpointAuthenticationResponseHandler oAuth2TokenEndpointAuthenticationResponseHandler() {
        return new DefaultOAuth2TokenEndpointAuthenticationResponseHandlerImpl();
    }
    /**
     * 自定义JWT编码上下文<br/>
     * 填充jwt.claims.sid为当前OP sessionId
     */
    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomer() {
        return new DefaultOidcTokenCustomer(this.oidcTokenCustomerExtend);
    }

    /**
     * 自定义客户端注册仓库 - JDBC实现
     *
     * @param jdbcTemplate jdbc连接模板
     */
    @Bean
    @ConditionalOnMissingBean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        return registeredClientRepository;
    }


    /**
     * 自定义认证信息管理服务 - JDBC+OIDC增强实现
     *
     * @param jdbcTemplate               jdbc连接模板
     * @param registeredClientRepository 客户端注册仓库
     */
    @Bean
    @ConditionalOnMissingBean
    public OidcAuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOidcAuthorizationService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 自定义授权确认服务 - JDBC实现
     *
     * @param jdbcTemplate               jdbc连接模板
     * @param registeredClientRepository 客户端注册仓库
     */
    @Bean
    @ConditionalOnMissingBean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 自定义JWK秘钥对
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
        RSAKey rsaKey = Jwks.convertRsaKey(this.oauth2ServerProps);
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }

    /**
     * 自定义JwtEncoder（兼容原逻辑），解决当前配置类中无法获取JwtEncoder问题
     * @param jwkSource 自定义JWK秘钥对
     */
    @Bean
    public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
        //定义参见OAuth2ConfigureUtils.getJwtEncoder
        return new NimbusJwsEncoder(jwkSource);
    }

    /**
     * CORS过滤器配置
     */
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource corsSource = new UrlBasedCorsConfigurationSource();
        corsSource.registerCorsConfiguration("/**", this.oauth2ServerProps.getCors());
        return new CorsFilter(corsSource);
    }

    /**
     * 配置 Provider元数据
     */
    @Bean
    public ProviderSettings providerSettings() {
        return ProviderSettings.builder()
                .issuer(this.oauth2ServerProps.getIssuer())
                .setting(PROVIDER_SETTINGS.END_SESSION_ENDPOINT, this.oauth2ServerProps.getEndSessionEndpoint())
                .authorizationEndpoint(this.oauth2ServerProps.getAuthorizationEndpoint())
                .tokenEndpoint(this.oauth2ServerProps.getTokenEndpoint())
                .jwkSetEndpoint(this.oauth2ServerProps.getJwkSetEndpoint())
                .oidcUserInfoEndpoint(this.oauth2ServerProps.getOidcUserInfoEndpoint())
                .tokenIntrospectionEndpoint(this.oauth2ServerProps.getTokenIntrospectionEndpoint())
                .tokenRevocationEndpoint(this.oauth2ServerProps.getTokenRevocationEndpoint())
                .build();
    }


    /**
     * 设置密码解析器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        String idForEncoder = PasswordEncoderEnum.getDefaultPasswordEncoderEnum(this.oauth2ServerProps.getPasswordEncoder()).getId();
        log.debug("default password encoder: {}", idForEncoder);
        return new DelegatingPasswordEncoder(idForEncoder, PasswordEncoderEnum.toIdToPasswordEncoderMap());
    }

}
